package com.gill.touka.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.gill.touka.domain.dto.UserDTO;
import com.gill.touka.feign.client.BaiduFeignClient;
import com.gill.touka.feign.client.UserCenterFeignClient;
import com.gill.touka.util.JwtOperator;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RefreshScope
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private UserCenterFeignClient feignClient;

    @Autowired
    private BaiduFeignClient baiduFeignClient;

    @Autowired
    private JwtOperator jwtOperator;

    @GetMapping("/feign/get")
    public UserDTO testGet(UserDTO userDTO){
        return feignClient.testGet(userDTO);
    }

    @PostMapping("/feign/post")
    public UserDTO testPost(@RequestBody UserDTO userDTO){
        return feignClient.testPost(userDTO);
    }

    @GetMapping("/feign/baidu")
    public String test1(){
        return baiduFeignClient.index();
    }

    /**
     * 测试：sentinel通过代码方式，而不是控制台方式配置流控规则
     * 复制README.md参考文章的代码，访问localhost:8082/sentinel会调用initFlowQpsRule方法，
     * 执行initFlowQpsRule方法后流控规则就会生效，可以在sentinel控制台查看
     */
    @GetMapping("/sentinel-rule")
    public String test2(){
        return "success";
    }
    @GetMapping("/sentinel")
    public String test3(){
        initFlowQpsRule("/test/sentinel-rule");
        return "已经为/sentinel-rule配置了流控规则";
    }
    private void initFlowQpsRule(String resourceName) {//配置流控规则的资源路径路径
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule(resourceName);
        // set limit qps to 20
        rule.setCount(20);
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setLimitApp("default");
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }

    /**
     * 目的：采用sentinel注解方式来重构之前的代码
     * 功能：@SentinelResource可以区分进行是“限流还是降级”，同时，默认可以捕获Throwable（所有异常），不需要使用Trace，缺点（不支持指定来源）
     * 测试方法：
     * 1、访问/sentinel/api带参数a和不带参数a，服务端不会做任何限流、降级处理
     * 2、在控制台为该资源添加qps的流控规则，快速访问带参数a，发现进行限流处理
     * 3、在控制台为该资源添加异常占比的限流规则，快速访问不带参数a，发现进行降级处理
     */
    @GetMapping("/sentinel/api")
    @SentinelResource(value = "define-a-resource",blockHandler = "block",fallback = "fallback")
    public String test4(@RequestParam(required = false) String a){//加个参数，好做测试
        // 1、执行业务
        if(StringUtils.isBlank(a)){
            throw new IllegalArgumentException();//（1）出异常会被fallback捕获，进行异常占比的降级处理
        }
        return "正常执行,未被降级、限流";//（2）正常执行，访问量过高可能会出现限流处理
    }
    //限流处理（必须具有相同类型的返回值和参数）
    public String block(String a,BlockException e){
        e.printStackTrace();
        return "被限流了";
    }
    //降级处理（必须具有相同类型的返回值和参数
    public String fallback(String a){//sentinel 1.6 可以处理所有异常，才可以加异常参数，但本次使用的cloud alibab对应的sentinel版本是1.5
        return "被降级了";
    }


    /**
     * 测试目的：为restTemplate整合sentinel
     * 测试结果：sentinel控制台能够监控restTemplate发送的请求
     * 测试方式：
     * 1、启动user、content，访问localhost:8081/test/sentinel/rest
     * 2、查看sentinel控制台是否监控到该资源信息
     * 3、为该资源配置流控规则进行测试
     */
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/sentinel/rest")
    public UserDTO test4(){
        return restTemplate.getForObject("http://user-center/users/1",UserDTO.class);
    }

    /**
     * 测试目的：为feign整合sentinel，使得feign发起的RPC请求都能被sentinel控制台监控
     * 测试结果：sentinel控制台设置流控规则，查看是否能触发feign配置的fallback方法
     */
    @GetMapping("/sentinel/feign")
    public UserDTO test5(){
        return feignClient.findById(1);
    }

    /**
     * 测试stream组件发送消息
     */
    @Autowired
    private Source source;

    @GetMapping("/stream")
    public String test6(){
        source.output().send(
                MessageBuilder
                        .withPayload("消息体")
                        .build()
        );
        return "stream简单使用案例";
    }

    /**
     * 测试目的：采用restTemplate的exchange方法传送token
     * 测试方案：
     * 1、在header设置token，访问localhost:8082/test/token/rest/exchange/{userId}
     * 2、restTemplate的exchange方法会将token传递给远程服务接口
     * 3、若header未设置token，则exchange传送的token为null，会报401
     */
    @GetMapping("/token/rest/exchange/{userId}")
    public ResponseEntity<UserDTO> tokenRelay(@PathVariable Integer userId, HttpServletRequest request) {
        String token = request.getHeader("X-Token");
        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Token", token);
        // 使用exchange方法传送token
        return this.restTemplate
                .exchange(
                        "http://user-center/users/{userId}",
                        HttpMethod.GET,
                        new HttpEntity<>(headers),
                        UserDTO.class,
                        userId
                );
    }

    /**
     * 测试目的：采用restTemplate的Interceptor传送token
     * 测试方案：
     * 1、访问该路径，若不配置拦截器，无论带不带token都不会使得restTemplate传递token给远程服务
     * 2、配置拦截器，带了token就可以访问远程服务
     */
    @GetMapping("/token/rest/interceptor/{userId}")
    public UserDTO test(@PathVariable Integer userId) {
        return this.restTemplate
                .getForObject(
                        "http://user-center/users/{userId}",
                        UserDTO.class, userId);
    }

    /**
     * 模拟生成权限为admin的token，用来测试admin相关接口
     *
     * @return token
     */
    @GetMapping("/gen-token")
    public String genToken() {
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("id", 1);
        userInfo.put("userName", "gill");
        userInfo.put("role", "admin");
        return jwtOperator.generateToken(userInfo);
    }


    /**
     * 从nacos获取配置文件数据
     */
    @Value("${your.configuration}")
    private String nacosConfig;

    @GetMapping("/nacos/config")
    public String nacosConfig(){
        return this.nacosConfig;
    }
}
